import io
from io import BytesIO
from minio import Minio
from minio.error import S3Error
from datetime import timedelta, datetime
from functools import wraps
from urllib.parse import quote
from docx import Document
from zipfile import is_zipfile
from loguru import logger

from bisheng.settings import settings
from bisheng.api.jsj_api.model.metro_fault import ConfigDao

def singleton(cls):
    instances = {}
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class SimpleMinioClient:
    def __init__(self):
        self.endpoint = "minio:9000"
        self.client = Minio(
            endpoint=self.endpoint,
            access_key=settings.get_knowledge().get('minio').get('MINIO_ACCESS_KEY'),
            secret_key=settings.get_knowledge().get('minio').get('MINIO_SECRET_KEY'),
            secure=settings.get_knowledge().get('minio').get('SCHEMA'),
        )

    def upload_file(self, bucket_name, object_name, file_path):
        """
        上传文件到 MinIO
        :param bucket_name: 桶名称
        :param object_name: 对象名称（在 MinIO 中的文件名）
        :param file_path: 本地文件路径
        :return: 如果上传成功返回 True，否则返回 False
        """
        try:
            self.client.fput_object(bucket_name, object_name, file_path)
            return True
        except S3Error as e:
            print(f"Error uploading file: {e}")
            return False

    def upload_file_by_io(self, bucket_name, object_name, output: io.BytesIO):
        """
        上传文件到 MinIO
        :param bucket_name: 桶名称
        :param object_name: 对象名称（在 MinIO 中的文件名）
        :param file_path: 本地文件路径
        :return: 如果上传成功返回 True，否则返回 False
        """
        data_length = output.getbuffer().nbytes  # 获取数据长度
        output.seek(0)  # 将文件指针重置到文件开头
        try:
            self.client.put_object(bucket_name, object_name, output,
            length = data_length,
            content_type = "application/octet-stream")
            print(f"File '{object_name}' uploaded to MinIO successfully.")
            return True
        except Exception as e:
            print(f"Error uploading file to MinIO: {e}")
            return False


    def generate_url(self, bucket_name, object_name, expires=timedelta(days=7)):
        """
        生成文件的临时访问 URL
        :param bucket_name: 桶名称
        :param object_name: 对象名称
        :param expires: URL 的有效期，默认为 7 天
        :return: 临时访问 URL
        """
        try:
            return self.client.presigned_get_object(bucket_name, object_name, expires=expires)
        except S3Error as e:
            print(f"Error generating URL: {e}")
            return None

    def generate_download_url(self, bucket_name, object_name):
        """
        生成文件的下载 URL
        :param bucket_name: 桶名称
        :param object_name: 对象名称（文件名）
        :return: 下载 URL
        """
        # URL 编码对象名称，以处理特殊字符
        encoded_object_name = quote(object_name)

        minio_url = (
            ConfigDao.get_config_by_key("minio_download_url").value
            if ConfigDao.get_config_by_key("minio_download_url")
            else self.endpoint
        )

        # 构建完整的 URL
        download_url = f"http://{minio_url}/{bucket_name}/{encoded_object_name}"

        return download_url

    def download_file(self, bucket_name, object_name, file_path):
        """
        从 MinIO 下载文件
        :param bucket_name: 桶名称
        :param object_name: 对象名称
        :param file_path: 保存文件的本地路径
        :return: 如果下载成功返回 True，否则返回 False
        """
        try:
            self.client.fget_object(bucket_name, object_name, file_path)
            return True
        except S3Error as e:
            print(f"Error downloading file: {e}")
            return False

    def process_word_document(
            self,
            source_bucket: str,
            source_object: str,
            placeholders: dict,  # 新增占位符字典参数
            dest_bucket: str = None,
            new_object_name: str = None,
            placeholder_prefix: str = "${",  # 新增前缀配置
            placeholder_suffix: str = "}"  # 新增后缀配置
    ) -> str:
        """
        处理Word文档并生成新版本（增强版）

        :param source_bucket: 源文件所在存储桶
        :param source_object: 源文件对象名称
        :param placeholders: 占位符字典 {占位符名称: 替换值}
        :param dest_bucket: 目标存储桶（默认与源相同）
        :param new_object_name: 新文件名（默认自动生成）
        :param placeholder_prefix: 占位符前缀（默认"${"）
        :param placeholder_suffix: 占位符后缀（默认"}"）
        :return: 新文件的下载URL
        """
        try:
            # 参数校验
            if not isinstance(placeholders, dict):
                logger.error(f"placeholders必须是字典类型")
                return ""

            # 初始化默认值
            dest_bucket = dest_bucket or source_bucket
            file_ext = source_object.split('.')[-1]
            new_object_name = new_object_name or \
                              f"processed_{datetime.now().strftime('%Y%m%d%H%M%S')}.{file_ext}"
            # 下载文件并校验
            try:
                response = self.client.get_object(source_bucket, source_object)
                file_data = response.read()
            except S3Error as e:
                logger.error(f"文件下载失败: {e}")
            finally:
                response.close()
                response.release_conn()
            input_stream = BytesIO(file_data)
            input_stream.seek(0)

            # 验证文件格式
            if not is_zipfile(input_stream):
                raise ValueError("下载的文件不是有效的Word文档(.docx)")
            input_stream.seek(0)
            # 处理文档内容
            doc = Document(input_stream)
            full_placeholders = {
                f"{placeholder_prefix}{key}{placeholder_suffix}": str(value)
                for key, value in placeholders.items()
            }

            # 定义替换函数
            def replace_text(text: str) -> str:
                for ph, val in full_placeholders.items():
                    text = text.replace(ph, val)
                return text

            # 替换段落内容
            for para in doc.paragraphs:
                for run in para.runs:
                    run.text = replace_text(run.text)
            # 替换表格内容
            for table in doc.tables:
                for row in table.rows:
                    for cell in row.cells:
                        for para in cell.paragraphs:
                            for run in para.runs:
                                run.text = replace_text(run.text)
            # 替换页眉
            for section in doc.sections:
                for header in section.header.paragraphs:
                    for run in header.runs:
                        run.text = replace_text(run.text)
            # 保存并上传
            output_stream = BytesIO()
            doc.save(output_stream)
            output_stream.seek(0)

            if self.upload_file_by_io(dest_bucket, new_object_name, output_stream):
                return self.generate_download_url(dest_bucket, new_object_name)
            logger.error(f"文件上传失败")
            return ""
        except (S3Error, ValueError) as e:
            logger.error(f"文档处理流程失败: {str(e)}", exc_info=True)
            return ""
        except Exception as e:
            logger.critical(f"未知错误: {str(e)}", exc_info=True)
            return ""
        finally:
            # 确保资源释放
            input_stream.close()
            output_stream.close()
